/*
 * Copyright 2011 Clean GWT
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package com.cleangwt.validation.client;

import java.util.ArrayList;
import java.util.List;

import com.google.gwt.user.client.ui.HasValue;

/**
 * The validation context for binding client input fields with {@link Validation}s.
 * 
 * @author Jess
 * @version 1.0
 * @since 1.0
 * @see Validator
 */
public class ValidationContext {

  /**
   * Checks whether the validation validator should be validated.
   * 
   * @param validatorGroups the validator groups
   * @param validateGroups the groups to valdiate
   * @return true to validate it, false to ignore the validation
   */
  private static boolean isGroupMatch(String[] validatorGroups, String[] validateGroups) {
    // the default group must be validated
    if (validatorGroups == null || validatorGroups.length == 0) {
      return true;
    }
    if (validateGroups == null || validateGroups.length == 0) {
      return false;
    }
    for (int i = 0; i < validatorGroups.length; i++) {
      for (int j = 0; j < validateGroups.length; j++) {
        if (validatorGroups[i].equals(validateGroups[j])) {
          return true;
        }
      }
    }
    return false;
  }

  private List<Validation> validaitons = new ArrayList<Validation>();
  private String errorMessage;
  private final HasValue<?>[] inputs;
  private boolean validated;

  /**
   * Creates a validation context with the given inputs.
   * 
   * @param inputs the inputs
   */
  ValidationContext(HasValue<?>[] inputs) {
    this.inputs = inputs;
  }

  /**
   * Adds a validation.
   * 
   * @param validation the validation
   * @return this for continuously building
   */
  public ValidationContext add(Validation validation) {
    assert this.inputs == null || inputs.length < 2 || validaitons.size() == 0 : "The multi-field context should only binds one validation";
    validaitons.add(validation);
    return this;
  }

  /**
   * Gets the error message.
   * 
   * @return the error message
   */
  String getErrorMessage() {
    return errorMessage;
  }

  /**
   * Gets the inputs.
   * 
   * @return the inputs
   */
  HasValue<?>[] getInputs() {
    return inputs;
  }

  /**
   * Retrieves the input values.
   * 
   * @return the input values
   */
  Object[] getInputValues() {
    if (inputs != null) {
      Object[] values = new Object[inputs.length];
      int i = 0;
      for (HasValue<?> input : inputs) {
        values[i++] = input.getValue();
      }
      return values;
    } else {
      return null;
    }
  }

  /**
   * Whether any input is bound.
   * 
   * @return whether any input is bound.
   */
  boolean hasInput() {
    return inputs != null && inputs.length > 0;
  }

  /**
   * Whether the given input is bound in this context.
   * 
   * @param input the input
   * @return whether the given input is bound in this context.
   */
  boolean hasInput(HasValue<?> input) {
    for (HasValue<?> i : inputs) {
      if (input == i) {
        return true;
      }
    }
    return false;
  }

  /**
   * Whether the context has been validated.
   * 
   * @return whether the context has been validated.
   */
  boolean isValidated() {
    return validated;
  }

  /**
   * Clears validation result.
   */
  void reset() {
    validated = false;
    errorMessage = null;
  }

  /**
   * Sets the error message as the given errorMessage.
   * 
   * @param errorMessage the error message to set
   */
  void setErrorMessage(String errorMessage) {
    this.errorMessage = errorMessage;
  }

  /**
   * Validates for the given validator and groups.
   * 
   * @param validator the validator
   * @param groups the groups
   * @return the error message
   */
  String validate(Validator validator, String[] groups) {
    boolean validated = false;
    String violation = null;
    Object[] values = null;
    for (Validation c : this.validaitons) {
      if (isGroupMatch(c.groups, groups)) {
        validated = true;
        if (values == null) {
          values = getInputValues();
        }
        if (c instanceof DeferredValidation) {
          ((DeferredValidation) c).initialize(new DeferredErrorReporter(validator, this));
        }
        violation = c.validate(values);
        if (violation != null) {
          break;
        }
      }
    }
    this.validated = validated;
    return violation;
  }

}
